using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Web.Configuration;
using System.Xml;

using Microsoft.SharePoint.Administration;

namespace Aks.Deployment.SharePoint.Configuration
{
	/// <summary>
	/// SharePoint configuration manager.
	/// </summary>
	public class ConfigurationManager
	{
		#region | Initialization |

		private const string SystemWebNodePath = "configuration/system.web";

		private readonly SPWebApplication _app;
		private readonly string _owner;


		/// <summary>
		/// Initializes a new instance of the <see cref="ConfigurationManager"/> class.
		/// </summary>
		/// <param name="app">The app.</param>
		/// <param name="modificationOwner">The modification owner.</param>
		public ConfigurationManager(SPWebApplication app, string modificationOwner)
		{
			this._app = app;
			this._owner = modificationOwner;
		}

		#endregion


		#region | system.web / compilation / assemblies |

		private const string CompilationAssembliesNodePath = SystemWebNodePath + "/compilation/assemblies";
		private const string CompilationAssembliesAddNodeFormat = @"<add assembly=""{0}"" />";


		/// <summary>
		/// Adds the compilation assembly.
		/// </summary>
		/// <param name="assemblyType">Class defined within the assembly to be registered.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager AddCompilationAssembly(Type assemblyType)
		{
			return AddCompilationAssembly(assemblyType.Assembly.FullName);
		}


		/// <summary>
		/// Adds the compilation assembly.
		/// </summary>
		/// <param name="assemblyFullName">Full name of the assembly.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager AddCompilationAssembly(string assemblyFullName)
		{
			return AddChildNode(CompilationAssembliesNodePath, String.Format(CompilationAssembliesAddNodeFormat, assemblyFullName));
		}

		#endregion


		#region | system.web / xhtmlCompliance |

		private const string XhtmlConformanceNode = "xhtmlConformance";
		private const string XhtmlConformanceNodePath = SystemWebNodePath + "/" + XhtmlConformanceNode;
		private const string XhtmlConformanceNodeXml = "<" + XhtmlConformanceNode + " />";
		private const string XhtmlConformanceModeAttribute = "mode";


		/// <summary>
		/// Sets the XHTML conformance.
		/// </summary>
		/// <param name="mode">The XHTML conformance mode.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager SetXhtmlConformance(XhtmlConformanceMode mode)
		{
			AddChildNode(XhtmlConformanceNode, SystemWebNodePath, XhtmlConformanceNodeXml);
			return SetAttribute(XhtmlConformanceNodePath, XhtmlConformanceModeAttribute, mode.ToString());
		}

		#endregion


		#region | Add Child Node |

		/// <summary>
		/// Adds a new child node web.config modification.
		/// </summary>
		/// <param name="path">The xpath expression indicating the desired configuration node.</param>
		/// <param name="value">The XML value to change.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager AddChildNode(string path, string value)
		{
			return AddChildNode(null, path, value);
		}


		/// <summary>
		/// Adds a new child node web.config modification.
		/// </summary>
		/// <param name="name">The modification name.</param>
		/// <param name="path">The xpath expression indicating the desired configuration node.</param>
		/// <param name="value">The XML value to change.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager AddChildNode(string name, string path, string value)
		{
			// get valid name
			name = String.IsNullOrEmpty(name) ? GenerateModificationName(value) : name;

			// add web config mod to app
			var mod = new SPWebConfigModification
			{
				Name = name,
				Path = path,
				Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
				Owner = this._owner,
				Value = value,
				Sequence = 0
			};
			this._app.WebConfigModifications.Add(mod);

			// return config mgr to support chaining
			return this;
		}

		#endregion


		#region | Set Attribute |

		/// <summary>
		/// Sets the specified <paramref name="attribute"/> a new web.config modification.
		/// </summary>
		/// <param name="nodePath">The xpath expression indicating the desired configuration node to set the <paramref name="attribute"/> for.</param>
		/// <param name="attribute">The name of the configuration node attribute to set.</param>
		/// <param name="value">The attribute value to set.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager SetAttribute(string nodePath, string attribute, string value)
		{
			// validate params
			if (String.IsNullOrEmpty(nodePath)) { throw new ArgumentNullException("nodePath", "A node path is required in order to know what web.config setting should be changed."); }
			if (String.IsNullOrEmpty(attribute)) { throw new ArgumentNullException("attribute", "An attribute name is required in order to know what web.config setting should be changed."); }

			// add web config mod to app
			var mod = new SPWebConfigModification
			{
				Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute,
				Owner = this._owner,
				Path = nodePath,
				Name = attribute,
				Value = value,
				Sequence = 0
			};
			this._app.WebConfigModifications.Add(mod);

			// return config mgr to support chaining
			return this;
		}

		#endregion


		#region | Methods [Public] |

		/// <summary>
		/// Determines whether any web.config modifications exist for the specified <see name="_owner"/>.
		/// </summary>
		/// <returns><c>true</c> if a web.config modification already exists; otherwise, <c>false</c>.</returns>
		public bool ContainsAny()
		{
#if net35
			return ((from m in this._app.WebConfigModifications.AsEnumerable() where m.Owner == owner select m).Count() > 0);
#else
			foreach (var mod in this._app.WebConfigModifications)
			{
				if (String.Compare(mod.Owner, this._owner, StringComparison.CurrentCultureIgnoreCase) == 0)
				{
					return true;
				}
			}
			return false;
#endif
		}


		/// <summary>
		/// Removes all web.config modifications with the specified <see name="_owner"/>.
		/// </summary>
		/// <returns></returns>
		public ConfigurationManager RemoveAll()
		{
			var mods = this._app.WebConfigModifications;
			for (var i = mods.Count - 1; i >= 0; i--)
			{
				var mod = mods[i];
				if (String.Compare(mod.Owner, this._owner, StringComparison.CurrentCultureIgnoreCase) == 0)
				{
					mods.RemoveAt(i);
				}
			}
			return this;
		}


		/// <summary>
		/// Updates the web application changes and applies web.config modifications.
		/// </summary>
		/// <returns></returns>
		public ConfigurationManager Update()
		{
			this._app.Update();
			this._app.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
			return this;
		}

		#endregion


		#region | Methods [Private] |

		/// <summary>
		/// Generates a valid modification name based on the <paramref name="modificationXml"/>.
		/// </summary>
		/// <param name="modificationXml">The XML to be added within the modification.</param>
		/// <returns>A valid modification name based on the modification XML.</returns>
		private static string GenerateModificationName(string modificationXml)
		{
			var doc = new XmlDocument { InnerXml = modificationXml };
			var node = doc.ChildNodes[0];
			var nameBuilder = new StringBuilder();
			nameBuilder.Append(node.Name);
			for (var i = 0; i < node.Attributes.Count; i++)
			{
				nameBuilder.AppendFormat(@"[@{0}=""{1}""]", node.Attributes[i].Name, node.Attributes[i].Value);
			}
			return nameBuilder.ToString();
		}

		#endregion
	}
}